Skip to content

Fix FSDP2 defaulting to version 1 in TrainingArguments; add dynamic plugin param passthrough - #42521

Closed
amanzoni1 wants to merge 7 commits into
huggingface:mainfrom
amanzoni1:fix-fsdp2-default-version
Closed

amanzoni1 wants to merge 7 commits into
huggingface:mainfrom
amanzoni1:fix-fsdp2-default-version

Conversation

@amanzoni1

Copy link
Copy Markdown

What does this PR do?

Fixes the bug where TrainingArguments(fsdp=True, fsdp_config={"fsdp_version": 2, ...}) defaults to FSDP version 1, ignoring the version (unless the Accelerator was initialized manually), and most params were lost.
Adds dynamic passthrough of all FSDP plugin params (FullyShardedDataParallelPlugin) from fsdp_config to the plugin args (future-proof, no hardcoding).

Changes

  • Extract fsdp_version pre-stripping to preserve it.
  • Skip stripping for "fsdp_version" to avoid mangling.
  • Dynamic copy from FullyShardedDataParallelPlugin dataclass for all fields.
  • Add test_fsdp_version_2_config for regression.

Repro

from transformers import TrainingArguments

args = TrainingArguments(
    output_dir="/tmp/test",
    fsdp=True,
    fsdp_config={
        "fsdp_version": 2,
        "reshard_after_forward": True,
    },
)

# Call the internal method to test argument parsing
plugin_args = args._process_fsdp_args()

# Expected output after this PR:
print(f"fsdp_version: {plugin_args['fsdp_version']}")                      # Before: 1, After: 2
print(f"reshard_after_forward: {plugin_args['reshard_after_forward']}")    # Before: None, After: True

Before submitting

Who can review?

@SunMarc @3outeille

Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
members/contributors who may be interested in your PR.

@amanzoni1
amanzoni1 force-pushed the fix-fsdp2-default-version branch 2 times, most recently from f19d66a to bcd3599 Compare December 2, 2025 11:16

@SunMarc SunMarc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks ! Left a few comments

Comment thread src/transformers/training_args.py Outdated
Comment on lines -2745 to -2762
fsdp_plugin_args["fsdp_version"] = self.fsdp_config.get("fsdp_version", 1)
prefetch_policy = self.fsdp_config.get("backward_prefetch", "NO_PREFETCH")
fsdp_plugin_args["backward_prefetch"] = prefetch_policy.upper()
fsdp_plugin_args["forward_prefetch"] = str(self.fsdp_config.get("forward_prefetch", "false")).lower()

sync_module_states = str(self.fsdp_config.get("sync_module_states", "true")).lower()
cpu_ram_efficient_loading = str(self.fsdp_config.get("cpu_ram_efficient_loading", "false")).lower()
if sync_module_states == "false" and cpu_ram_efficient_loading == "true":
# In this case, all the processes except the main process would have random weights leading
# to unexpected behaviour during training, thus throwing error here to prevent it.
raise ValueError('`sync_module_states` must be `"True"` if `cpu_ram_efficient_loading` is `"True"`')

# we need to set the env here as otherwise we get a warning in accelerate + we need to set it for transformers
fsdp_plugin_args["cpu_ram_efficient_loading"] = cpu_ram_efficient_loading
os.environ["FSDP_CPU_RAM_EFFICIENT_LOADING"] = cpu_ram_efficient_loading

fsdp_plugin_args["sync_module_states"] = sync_module_states
fsdp_plugin_args["use_orig_params"] = str(self.fsdp_config.get("use_orig_params", "true")).lower()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be better to keep this for now as we can easily change default config here instead of doing it in accelerate. Also, some defaults are not the same here vs accelerate. Nevertheless, for keys that are not exposed here but we have them in FullyShardedDataParallelPlugin, it's fine to set them.

Comment on lines +2755 to +2756
if key in self.fsdp_config and key not in fsdp_plugin_args:
fsdp_plugin_args[key] = self.fsdp_config[key]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can keep this but put it at the end.

Comment on lines +2736 to +2739
# Set deprecated sharding_strategy from CLI (plugin maps to reshard_after_forward)
# Skip if config has explicit reshard_after_forward (prioritize config)
if "reshard_after_forward" not in self.fsdp_config:
fsdp_plugin_args["sharding_strategy"] = fsdp_option

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks !

if self.fsdp_config is not None and isinstance(self.fsdp_config, dict):
for k in list(self.fsdp_config.keys()):
if k.startswith("fsdp_"):
if k.startswith("fsdp_") and k != "fsdp_version":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed thanks !

Comment thread tests/fsdp/test_fsdp.py
Comment on lines +214 to +232
def test_fsdp_version_2_config(self):
output_dir = self.get_auto_remove_tmp_dir()
kwargs = {
"output_dir": output_dir,
"train_len": 128,
"save_steps": 5,
"learning_rate": 0.1,
"fsdp": True,
"fsdp_config": {
"fsdp_version": 2,
"reshard_after_forward": True,
},
}
with mockenv_context(**self.dist_env_1_gpu):
trainer = get_regression_trainer(**kwargs)
plugin_args = trainer.args._process_fsdp_args()
self.assertEqual(plugin_args["fsdp_version"], 2)
self.assertTrue(plugin_args["reshard_after_forward"])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

@amanzoni1

Copy link
Copy Markdown
Author

Thanks for the feedback @SunMarc!!
implemented the hardcoded defaults + dynamic copy at end, and needed to add a conditional skip for forward_prefetch in v2 (Accelerate raises ValueError otherwise: "ValueError: forward_prefetch is not yet implemented in FSDP2, set to None or use fsdp_version=1").
Let me know if that can works

@SunMarc

SunMarc commented Dec 2, 2025 •

Copy link
Copy Markdown
Member

I've merged this PR that seems to fix a bit more things ! Feel free to rebase and add your updates, especially the test is nice !

#42566

@amanzoni1
amanzoni1 force-pushed the fix-fsdp2-default-version branch from 9a8af59 to b6a9f0b Compare December 2, 2025 20:10
@amanzoni1

Copy link
Copy Markdown
Author

@SunMarc Thanks for the feedback! Rebased onto the latest main, and ready for review

@SunMarc SunMarc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, just a few nits !

Comment thread src/transformers/training_args.py Outdated
Comment on lines +2782 to +2789
# HF-to-plugin map
if (
"transformer_layer_cls_to_wrap" in self.fsdp_config
and "transformer_cls_names_to_wrap" not in fsdp_plugin_args
):
fsdp_plugin_args["transformer_cls_names_to_wrap"] = ",".join(
self.fsdp_config["transformer_layer_cls_to_wrap"]
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not need anymore

Comment thread tests/fsdp/test_fsdp.py
Comment on lines +222 to +225
"fsdp_config": {
"fsdp_version": 2,
"reshard_after_forward": True,
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add more things in the config so that it is better tested ?

@amanzoni1
amanzoni1 force-pushed the fix-fsdp2-default-version branch from 61c0e74 to 2252af4 Compare December 3, 2025 15:33

@SunMarc SunMarc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks !

@SunMarc
SunMarc enabled auto-merge (squash) December 3, 2025 15:50
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

auto-merge was automatically disabled December 3, 2025 16:10

Head branch was pushed to by a user without write access

@amanzoni1
amanzoni1 force-pushed the fix-fsdp2-default-version branch from 3761cdd to ff47d0d Compare December 3, 2025 16:10
@SunMarc
SunMarc enabled auto-merge (squash) December 3, 2025 16:53
@amanzoni1

Copy link
Copy Markdown
Author

Hey @SunMarc, not sure if there's anything more I should do here. The failed CI doesn't seem code related. Thanks!

@SunMarc

SunMarc commented Dec 8, 2025

Copy link
Copy Markdown
Member

cc @ArthurZucker can you merge ?

@github-actions

github-actions Bot commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

View the CircleCI Test Summary for this PR:

https://huggingface.co/spaces/transformers-community/circle-ci-viz?pr=42521&sha=661f54

@github-actions

github-actions Bot commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

View the CircleCI Test Summary for this PR:

https://huggingface.co/spaces/transformers-community/circle-ci-viz?pr=42521&sha=dd3f32

jiqing-feng pushed a commit to jiqing-feng/transformers that referenced this pull request May 20, 2026
huggingface#45640)

* FSDP: default to v2, simplify API around fsdp + fsdp_config

- TrainingArguments.fsdp is now a boolean on-switch. String / list values
  are still accepted for backward compatibility but are translated into
  fsdp_config entries (and emit a deprecation warning).
- fsdp_config now defaults to FSDP2 (version=2). FSDP1 usage still works
  but logs a deprecation warning (to be removed in v5.20).
- New fsdp_config keys: auto_wrap_policy (default TRANSFORMER_BASED_WRAP),
  cpu_offload, state_dict_type (default FULL_STATE_DICT so
  trainer.save_model() produces an HF-compatible checkpoint out of the
  box).
- FSDP1-only handling (forward_prefetch, backward_prefetch, use_orig_params,
  sync_module_states, and the string form of reshard_after_forward) is
  now isolated in a single branch for easy removal once v1 support is
  dropped. Legacy `fsdp` string/list parsing lives in a single
  _apply_legacy_fsdp_to_config helper for the same reason.
- Trainer reads args.fsdp / args.fsdp_config guarded so that running
  without FSDP (or via `accelerate launch` with no transformers-side
  config) no longer crashes on None.
- Docstring rewritten around fsdp_config; FSDP2-only keys surfaced,
  FSDP1-only keys (sync_module_states, use_orig_params, limit_all_gathers)
  dropped from the public docstring.

Verified on the repro from PR huggingface#42521:

    TrainingArguments(fsdp=True, fsdp_config={"fsdp_version": 2,
                                              "reshard_after_forward": True})

now produces fsdp_version=2 and reshard_after_forward=True.

* Apply repo consistency fixes

* Cleanup: drop unused bool() casts and stale comment

- `args.fsdp` is already a bool after `_process_fsdp_args`, so wrapping
  it with `bool(...)` in trainer.py was redundant.
- Remove the explanatory comment above `state_dict_type` default; the
  docstring already covers it.

* Forward activation_checkpointing / limit_all_gathers via plugin kwargs

Drop the post-accelerator `setattr` loop that re-pushed these two keys
onto the already-constructed FSDP plugin. They are now forwarded during
plugin construction via `fsdp_plugin_args`:

- `activation_checkpointing` is shared between FSDP1 and FSDP2.
- `limit_all_gathers` is FSDP1-only (obsolete in FSDP2), so it lives in
  the v1 branch.

The `activation_checkpointing` + `gradient_checkpointing` conflict check
stays in trainer.py (still needs the post-plugin state to compare).

* Apply suggestions from code review

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* fix

* Apply repo consistency fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
yuchenxie4645 pushed a commit to yuchenxie4645/transformers that referenced this pull request May 28, 2026
huggingface#45640)

* FSDP: default to v2, simplify API around fsdp + fsdp_config

- TrainingArguments.fsdp is now a boolean on-switch. String / list values
  are still accepted for backward compatibility but are translated into
  fsdp_config entries (and emit a deprecation warning).
- fsdp_config now defaults to FSDP2 (version=2). FSDP1 usage still works
  but logs a deprecation warning (to be removed in v5.20).
- New fsdp_config keys: auto_wrap_policy (default TRANSFORMER_BASED_WRAP),
  cpu_offload, state_dict_type (default FULL_STATE_DICT so
  trainer.save_model() produces an HF-compatible checkpoint out of the
  box).
- FSDP1-only handling (forward_prefetch, backward_prefetch, use_orig_params,
  sync_module_states, and the string form of reshard_after_forward) is
  now isolated in a single branch for easy removal once v1 support is
  dropped. Legacy `fsdp` string/list parsing lives in a single
  _apply_legacy_fsdp_to_config helper for the same reason.
- Trainer reads args.fsdp / args.fsdp_config guarded so that running
  without FSDP (or via `accelerate launch` with no transformers-side
  config) no longer crashes on None.
- Docstring rewritten around fsdp_config; FSDP2-only keys surfaced,
  FSDP1-only keys (sync_module_states, use_orig_params, limit_all_gathers)
  dropped from the public docstring.

Verified on the repro from PR huggingface#42521:

    TrainingArguments(fsdp=True, fsdp_config={"fsdp_version": 2,
                                              "reshard_after_forward": True})

now produces fsdp_version=2 and reshard_after_forward=True.

* Apply repo consistency fixes

* Cleanup: drop unused bool() casts and stale comment

- `args.fsdp` is already a bool after `_process_fsdp_args`, so wrapping
  it with `bool(...)` in trainer.py was redundant.
- Remove the explanatory comment above `state_dict_type` default; the
  docstring already covers it.

* Forward activation_checkpointing / limit_all_gathers via plugin kwargs

Drop the post-accelerator `setattr` loop that re-pushed these two keys
onto the already-constructed FSDP plugin. They are now forwarded during
plugin construction via `fsdp_plugin_args`:

- `activation_checkpointing` is shared between FSDP1 and FSDP2.
- `limit_all_gathers` is FSDP1-only (obsolete in FSDP2), so it lives in
  the v1 branch.

The `activation_checkpointing` + `gradient_checkpointing` conflict check
stays in trainer.py (still needs the post-plugin state to compare).

* Apply suggestions from code review

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* fix

* Apply repo consistency fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
kashif pushed a commit to kashif/transformers that referenced this pull request Jun 1, 2026
huggingface#45640)

* FSDP: default to v2, simplify API around fsdp + fsdp_config

- TrainingArguments.fsdp is now a boolean on-switch. String / list values
  are still accepted for backward compatibility but are translated into
  fsdp_config entries (and emit a deprecation warning).
- fsdp_config now defaults to FSDP2 (version=2). FSDP1 usage still works
  but logs a deprecation warning (to be removed in v5.20).
- New fsdp_config keys: auto_wrap_policy (default TRANSFORMER_BASED_WRAP),
  cpu_offload, state_dict_type (default FULL_STATE_DICT so
  trainer.save_model() produces an HF-compatible checkpoint out of the
  box).
- FSDP1-only handling (forward_prefetch, backward_prefetch, use_orig_params,
  sync_module_states, and the string form of reshard_after_forward) is
  now isolated in a single branch for easy removal once v1 support is
  dropped. Legacy `fsdp` string/list parsing lives in a single
  _apply_legacy_fsdp_to_config helper for the same reason.
- Trainer reads args.fsdp / args.fsdp_config guarded so that running
  without FSDP (or via `accelerate launch` with no transformers-side
  config) no longer crashes on None.
- Docstring rewritten around fsdp_config; FSDP2-only keys surfaced,
  FSDP1-only keys (sync_module_states, use_orig_params, limit_all_gathers)
  dropped from the public docstring.

Verified on the repro from PR huggingface#42521:

    TrainingArguments(fsdp=True, fsdp_config={"fsdp_version": 2,
                                              "reshard_after_forward": True})

now produces fsdp_version=2 and reshard_after_forward=True.

* Apply repo consistency fixes

* Cleanup: drop unused bool() casts and stale comment

- `args.fsdp` is already a bool after `_process_fsdp_args`, so wrapping
  it with `bool(...)` in trainer.py was redundant.
- Remove the explanatory comment above `state_dict_type` default; the
  docstring already covers it.

* Forward activation_checkpointing / limit_all_gathers via plugin kwargs

Drop the post-accelerator `setattr` loop that re-pushed these two keys
onto the already-constructed FSDP plugin. They are now forwarded during
plugin construction via `fsdp_plugin_args`:

- `activation_checkpointing` is shared between FSDP1 and FSDP2.
- `limit_all_gathers` is FSDP1-only (obsolete in FSDP2), so it lives in
  the v1 branch.

The `activation_checkpointing` + `gradient_checkpointing` conflict check
stays in trainer.py (still needs the post-plugin state to compare).

* Apply suggestions from code review

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* fix

* Apply repo consistency fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
khushali9 pushed a commit to khushali9/transformers that referenced this pull request Jun 8, 2026
huggingface#45640)

* FSDP: default to v2, simplify API around fsdp + fsdp_config

- TrainingArguments.fsdp is now a boolean on-switch. String / list values
  are still accepted for backward compatibility but are translated into
  fsdp_config entries (and emit a deprecation warning).
- fsdp_config now defaults to FSDP2 (version=2). FSDP1 usage still works
  but logs a deprecation warning (to be removed in v5.20).
- New fsdp_config keys: auto_wrap_policy (default TRANSFORMER_BASED_WRAP),
  cpu_offload, state_dict_type (default FULL_STATE_DICT so
  trainer.save_model() produces an HF-compatible checkpoint out of the
  box).
- FSDP1-only handling (forward_prefetch, backward_prefetch, use_orig_params,
  sync_module_states, and the string form of reshard_after_forward) is
  now isolated in a single branch for easy removal once v1 support is
  dropped. Legacy `fsdp` string/list parsing lives in a single
  _apply_legacy_fsdp_to_config helper for the same reason.
- Trainer reads args.fsdp / args.fsdp_config guarded so that running
  without FSDP (or via `accelerate launch` with no transformers-side
  config) no longer crashes on None.
- Docstring rewritten around fsdp_config; FSDP2-only keys surfaced,
  FSDP1-only keys (sync_module_states, use_orig_params, limit_all_gathers)
  dropped from the public docstring.

Verified on the repro from PR huggingface#42521:

    TrainingArguments(fsdp=True, fsdp_config={"fsdp_version": 2,
                                              "reshard_after_forward": True})

now produces fsdp_version=2 and reshard_after_forward=True.

* Apply repo consistency fixes

* Cleanup: drop unused bool() casts and stale comment

- `args.fsdp` is already a bool after `_process_fsdp_args`, so wrapping
  it with `bool(...)` in trainer.py was redundant.
- Remove the explanatory comment above `state_dict_type` default; the
  docstring already covers it.

* Forward activation_checkpointing / limit_all_gathers via plugin kwargs

Drop the post-accelerator `setattr` loop that re-pushed these two keys
onto the already-constructed FSDP plugin. They are now forwarded during
plugin construction via `fsdp_plugin_args`:

- `activation_checkpointing` is shared between FSDP1 and FSDP2.
- `limit_all_gathers` is FSDP1-only (obsolete in FSDP2), so it lives in
  the v1 branch.

The `activation_checkpointing` + `gradient_checkpointing` conflict check
stays in trainer.py (still needs the post-plugin state to compare).

* Apply suggestions from code review

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* fix

* Apply repo consistency fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
stevhliu added a commit to stevhliu/transformers that referenced this pull request Jul 30, 2026
huggingface#45640)

* FSDP: default to v2, simplify API around fsdp + fsdp_config

- TrainingArguments.fsdp is now a boolean on-switch. String / list values
  are still accepted for backward compatibility but are translated into
  fsdp_config entries (and emit a deprecation warning).
- fsdp_config now defaults to FSDP2 (version=2). FSDP1 usage still works
  but logs a deprecation warning (to be removed in v5.20).
- New fsdp_config keys: auto_wrap_policy (default TRANSFORMER_BASED_WRAP),
  cpu_offload, state_dict_type (default FULL_STATE_DICT so
  trainer.save_model() produces an HF-compatible checkpoint out of the
  box).
- FSDP1-only handling (forward_prefetch, backward_prefetch, use_orig_params,
  sync_module_states, and the string form of reshard_after_forward) is
  now isolated in a single branch for easy removal once v1 support is
  dropped. Legacy `fsdp` string/list parsing lives in a single
  _apply_legacy_fsdp_to_config helper for the same reason.
- Trainer reads args.fsdp / args.fsdp_config guarded so that running
  without FSDP (or via `accelerate launch` with no transformers-side
  config) no longer crashes on None.
- Docstring rewritten around fsdp_config; FSDP2-only keys surfaced,
  FSDP1-only keys (sync_module_states, use_orig_params, limit_all_gathers)
  dropped from the public docstring.

Verified on the repro from PR huggingface#42521:

    TrainingArguments(fsdp=True, fsdp_config={"fsdp_version": 2,
                                              "reshard_after_forward": True})

now produces fsdp_version=2 and reshard_after_forward=True.

* Apply repo consistency fixes

* Cleanup: drop unused bool() casts and stale comment

- `args.fsdp` is already a bool after `_process_fsdp_args`, so wrapping
  it with `bool(...)` in trainer.py was redundant.
- Remove the explanatory comment above `state_dict_type` default; the
  docstring already covers it.

* Forward activation_checkpointing / limit_all_gathers via plugin kwargs

Drop the post-accelerator `setattr` loop that re-pushed these two keys
onto the already-constructed FSDP plugin. They are now forwarded during
plugin construction via `fsdp_plugin_args`:

- `activation_checkpointing` is shared between FSDP1 and FSDP2.
- `limit_all_gathers` is FSDP1-only (obsolete in FSDP2), so it lives in
  the v1 branch.

The `activation_checkpointing` + `gradient_checkpointing` conflict check
stays in trainer.py (still needs the post-plugin state to compare).

* Apply suggestions from code review

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* fix

* Apply repo consistency fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
@ArthurZucker

Copy link
Copy Markdown
Collaborator

Thanks a lot for the fix and your patience, @amanzoni1, and sorry this didn't get merged in time! Since then, #45640 rewrote how fsdp_config is turned into the Accelerate plugin and made FSDP2 the default (fsdp_config["version"] defaults to 2), so the version being ignored is fixed on main. tests/fsdp/test_fsdp.py was also replaced by tests/trainer/distributed/ in #44338. If some plugin options you needed are still not passed through, a small PR against main with a test under tests/trainer/distributed/ would be very welcome. Closing this one, thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants